Skip to content

feat: add Abnormal Security partner integration#682

Open
anoopabsec wants to merge 3 commits intochronicle:mainfrom
anoopabsec:add-abnormal-security-integration
Open

feat: add Abnormal Security partner integration#682
anoopabsec wants to merge 3 commits intochronicle:mainfrom
anoopabsec:add-abnormal-security-integration

Conversation

@anoopabsec
Copy link
Copy Markdown

@anoopabsec anoopabsec commented Apr 14, 2026

Summary

Adds the Abnormal Security response integration for Google SecOps SOAR, enabling automated email threat search and remediation through the Abnormal Security API.

Changes

  • Integration definition (definition.yaml): API URL, API Key, Verify SSL configuration parameters
  • Core manager (core/AbnormalManager.py): HTTP client with Bearer token auth, automatic retry on 429/5xx, custom exceptions
  • 4 SOAR Actions:
    • Ping — test connectivity and authentication
    • Search Messages — find threats by time range, sender email, or subject
    • Remediate Messages — delete/move/reclassify malicious messages; returns Activity Log ID
    • Get Activity Status — poll remediation status until terminal state (success/failed/completed)
  • Supporting files: pyproject.toml, uv.lock, release_notes.yaml, ontology_mapping.yaml, JSON result examples, SVG logo

Test Plan

  • mp validate integration abnormal_security22/22 validations passed
    Integration: abnormal_security | Passed: 22 | Executed: 22 / 22 validations
    
  • mp build integration abnormal_security — builds successfully
  • End-to-end testing in Google SecOps SOAR dev environment (requires dev instance access)

Deploy Plan

No relevant deploy plans — this is a new marketplace integration submitted via PR for Google SecOps review.

🤖 Generated with Claude Code


Note

Medium Risk
Introduces a new integration that can perform message remediation via an external API, so mistakes in request construction/validation could lead to unintended email actions. Impact is mostly isolated to the new abnormal_security integration package.

Overview
Adds a new AbnormalSecurity response integration, including configuration (API URL, API Key, Verify SSL) and packaging metadata (pyproject.toml, uv.lock, release_notes.yaml).

Implements four new SOAR actions—Ping, Search Messages, Remediate Messages, and Get Activity Status—backed by a new AbnormalManager HTTP client with Bearer auth, retry handling for 429/5xx, and input validation for remediation/status parameters, plus example JSON results and branding resources.

Reviewed by Cursor Bugbot for commit 4282490. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds the Abnormal Security response integration for Google SecOps SOAR.
This integration enables automated email threat search and remediation
via the Abnormal Security API.

Actions:
- Ping: Test connectivity and authentication
- Search Messages: Find email threats by time range, sender, or subject
- Remediate Messages: Delete/move/reclassify malicious messages
- Get Activity Status: Poll remediation operation status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@anoopabsec anoopabsec requested a review from a team as a code owner April 14, 2026 10:36
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new third-party integration for Google SecOps SOAR, allowing users to interact with the Abnormal Security API. The changes include the necessary configuration, core API management logic, and a suite of actions to support automated email security workflows, such as searching for threats and executing remediation actions.

Highlights

  • New Integration: Added a new Abnormal Security integration for Google SecOps SOAR to enable automated email threat search and remediation.
  • Core Functionality: Implemented a robust AbnormalManager with Bearer token authentication, automatic retry logic for rate limits and server errors, and custom exception handling.
  • SOAR Actions: Introduced four new actions: Ping (connectivity test), Search Messages (threat discovery), Remediate Messages (remediation submission), and Get Activity Status (remediation polling).

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@google-cla
Copy link
Copy Markdown

google-cla Bot commented Apr 14, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4282490. Configure here.

[tool.uv.sources]
tipcommon = { path = "../../../../../packages/tipcommon/whls/TIPCommon-1.1.0.1-py2.py3-none-any.whl" }
environmentcommon = { path = "../../../../../packages/envcommon/whls/EnvironmentCommon-1.0.2-py2.py3-none-any.whl" }
soar-sdk = { path = "/Users/anoop/soar-sdk" }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local developer path committed in pyproject.toml

High Severity

The soar-sdk source in pyproject.toml points to a hardcoded local path /Users/anoop/soar-sdk, and uv.lock also contains this path. Other integrations in the repo (e.g., anyrun_sandbox) correctly use soar-sdk = { git = "https://github.com/chronicle/soar-sdk.git" }. This will break builds for any other developer or CI environment.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4282490. Configure here.

)

messages = [m.strip() for m in messages_raw.split(",") if m.strip()]
tenant_ids = [t.strip() for t in tenant_ids_raw.split(",")] if tenant_ids_raw else None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent tenant ID parsing allows empty strings

Medium Severity

The tenant_ids parsing in RemediateMessages.py and SearchMessages.py does not filter out empty strings from the split result (e.g., "id1,,id2" produces ["id1", "", "id2"]). In contrast, GetActivityStatus.py correctly uses if t.strip() to filter them out. Empty strings in the tenant IDs list would be sent to the API, potentially causing errors or unexpected behavior.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4282490. Configure here.

)

messages = response.get("messages", [])
siemplify.result.add_result_json(json.dumps(response))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double JSON encoding in result output

Medium Severity

add_result_json is called with json.dumps(response) (a pre-serialized string), but other integrations in the repo (e.g., censys) pass raw Python objects directly. If add_result_json performs its own serialization internally, this produces double-encoded JSON — downstream consumers would receive a JSON string literal instead of a structured object.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4282490. Configure here.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a new Abnormal Security integration for Google SecOps SOAR, providing actions for connectivity testing, message searching, remediation, and status tracking. The feedback highlights several areas for improvement to meet repository standards, including the addition of mandatory unit tests, pinning the Python version range, and refactoring the API manager to use asynchronous I/O. Furthermore, corrections are required for standardized Ping action messages, proper TIPCommon submodule imports, and the prevention of double-encoded JSON results in action outputs. Ensuring all functions have complete docstrings and type annotations is also necessary for compliance.

@@ -0,0 +1,26 @@
[project]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This pull request is missing the required unit tests. The repository style guide is very strict that all new features, bug fixes, or integrations added to the content/response_integrations/** directory must include corresponding unit tests. Please add a tests/ directory with pytest tests for the new actions and manager, modeling them after the examples in content/response_integrations/third_party/telegram/tests/.

References
  1. All new features, bug fixes, or integrations added to content/response_integrations/** must include corresponding unit tests to ensure production stability. (link)

Comment on lines +63 to +65
output_message = (
f"Successfully connected to the {INTEGRATION_DISPLAY_NAME} API at {api_url}."
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The success message for the Ping action does not conform to the exact format required by the repository style guide. Using the standard format ensures consistent and predictable output for playbook designers.

Suggested change
output_message = (
f"Successfully connected to the {INTEGRATION_DISPLAY_NAME} API at {api_url}."
)
output_message = (
f"Successfully connected to the {INTEGRATION_DISPLAY_NAME} server with the provided "
f"connection parameters!"
)
References
  1. The Ping action success message must be exactly: "Successfully connected to the {integration name} server with the provided connection parameters!" (link)

)

activity_status = response.get("status", "unknown")
siemplify.result.add_result_json(json.dumps(response))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The add_result_json method handles the JSON serialization internally. By passing json.dumps(response), you are creating a double-encoded JSON string, which will likely cause issues in the playbook. Please pass the dictionary object directly.

Suggested change
siemplify.result.add_result_json(json.dumps(response))
siemplify.result.add_result_json(response)
References
  1. If an action returns a JSON result by calling result.add_result_json(...), it must be a valid JSON structure. Double-encoding can break playbook placeholders. (link)

)

messages = response.get("messages", [])
siemplify.result.add_result_json(json.dumps(response))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The add_result_json method handles the JSON serialization internally. By passing json.dumps(response), you are creating a double-encoded JSON string, which will likely cause issues in the playbook. Please pass the dictionary object directly.

Suggested change
siemplify.result.add_result_json(json.dumps(response))
siemplify.result.add_result_json(response)
References
  1. If an action returns a JSON result by calling result.add_result_json(...), it must be a valid JSON structure. Double-encoding can break playbook placeholders. (link)

)

activity_log_id = response.get("activity_log_id", "")
siemplify.result.add_result_json(json.dumps(response))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The add_result_json method handles the JSON serialization internally. By passing json.dumps(response), you are creating a double-encoded JSON string, which will likely cause issues in the playbook. Please pass the dictionary object directly.

Suggested change
siemplify.result.add_result_json(json.dumps(response))
siemplify.result.add_result_json(response)
References
  1. If an action returns a JSON result by calling result.add_result_json(...), it must be a valid JSON structure. Double-encoding can break playbook placeholders. (link)



@output_handler
def main():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The main function is missing a docstring and a return type annotation, which is required by the repository style guide. All functions must have docstrings and be fully type-annotated.

Suggested change
def main():
@output_handler
def main() -> None:
"""
Main execution logic for the Search Messages action.
"""
References
  1. All function parameters and return types must be annotated. Use triple double quotes for all function docstrings. (link)

Comment on lines +99 to +114
except AbnormalValidationError as e:
output_message = f"Invalid parameters: {e}"
siemplify.LOGGER.error(output_message)

except AbnormalAuthenticationError as e:
output_message = f"Authentication failed: {e}"
siemplify.LOGGER.error(output_message)

except AbnormalConnectionError as e:
output_message = f"Connection error: {e}"
siemplify.LOGGER.error(output_message)

except Exception as e:
output_message = f"An unexpected error occurred: {e}"
siemplify.LOGGER.error(output_message)
siemplify.LOGGER.exception(e)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error messages in the except blocks do not follow the standard format specified in the style guide. For consistency in playbook outputs, please prefix error messages with Error executing action "{action name}". Reason: {error}.

Suggested change
except AbnormalValidationError as e:
output_message = f"Invalid parameters: {e}"
siemplify.LOGGER.error(output_message)
except AbnormalAuthenticationError as e:
output_message = f"Authentication failed: {e}"
siemplify.LOGGER.error(output_message)
except AbnormalConnectionError as e:
output_message = f"Connection error: {e}"
siemplify.LOGGER.error(output_message)
except Exception as e:
output_message = f"An unexpected error occurred: {e}"
siemplify.LOGGER.error(output_message)
siemplify.LOGGER.exception(e)
except AbnormalValidationError as e:
output_message = f'Error executing action "{siemplify.script_name}". Reason: {e}'
siemplify.LOGGER.error(output_message)
except AbnormalAuthenticationError as e:
output_message = f'Error executing action "{siemplify.script_name}". Reason: {e}'
siemplify.LOGGER.error(output_message)
except AbnormalConnectionError as e:
output_message = f'Error executing action "{siemplify.script_name}". Reason: {e}'
siemplify.LOGGER.error(output_message)
except Exception as e:
output_message = f'Error executing action "{siemplify.script_name}". Reason: An unexpected error occurred: {e}'
siemplify.LOGGER.error(output_message)
siemplify.LOGGER.exception(e)
References
  1. Error messages should follow a standard template for consistency. The required prefix is Error executing action "{action name}". Reason: {error}. (link)

"""Raised when input validation fails."""


class AbnormalManager:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The style guide recommends using asyncio with httpx or aiohttp for I/O-bound operations like network requests. This manager uses the synchronous requests library. While functional, refactoring to an asynchronous implementation would better align with the repository's modern Python patterns and improve performance in high-throughput SecOps workflows.

References
  1. Since many SecOps workflows are I/O bound (API calls, logs), we leverage asyncio. Use async and await for network requests (e.g., using httpx or aiohttp). (link)



@output_handler
def main():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The main function is missing a docstring and a return type annotation, which is required by the repository style guide. All functions must have docstrings and be fully type-annotated.

Suggested change
def main():
@output_handler
def main() -> None:
"""
Main execution logic for the Get Activity Status action.
"""
References
  1. All function parameters and return types must be annotated. Use triple double quotes for all function docstrings. (link)

name = "AbnormalSecurity"
version = "1.0"
description = "Abnormal Security uses AI to protect organizations from email attacks. This integration enables automated search and remediation of malicious email messages through the Abnormal Security API. For support, contact: support@abnormalsecurity.com"
requires-python = ">=3.11"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The requires-python value does not match the repository's standard. To ensure compatibility and prevent unexpected issues from future Python minor releases, please pin the upper version.

Suggested change
requires-python = ">=3.11"
requires-python = ">=3.11,<3.12"
References
  1. The requires-python dependency in pyproject.toml should be pinned to a specific minor version range, e.g., ">=3.11,<3.12". (link)

@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 15, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@github-actions
Copy link
Copy Markdown

Marketplace Validation Failed

Click to view the full report

Validation Report

🧩 Integrations
Pre-Build Stage

abnormal_security

Validation Name Details
⚠️ Uv Lock Error happened while executing a command: uv lock --check: Using CPython 3.11.15 interpreter at: /opt/hostedtoolcache/Python/3.11.15/x64/bin/python error: Failed to generate package metadata for soar-sdk==0.2.0 @ directory+/Users/anoop/soar-sdk Caused by: Distribution not found at: file:///Users/anoop/soar-sdk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants